package com.lds.config.center.server.client.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.lds.config.center.client.common.model.request.GetAllItemRequest;
import com.lds.config.center.client.common.model.request.NotifyUpdateItemRequest;
import com.lds.config.center.client.common.model.response.GetAllItemResponse;
import com.lds.config.center.client.common.model.response.NotifyUpdateItemResponse;
import com.lds.config.center.client.common.util.WatchKeyUtil;
import com.lds.config.center.server.business.app.entity.App;
import com.lds.config.center.server.business.app.mapper.AppMapper;
import com.lds.config.center.server.business.cluster.entity.Cluster;
import com.lds.config.center.server.business.cluster.mapper.ClusterMapper;
import com.lds.config.center.server.business.env.entity.Env;
import com.lds.config.center.server.business.env.mapper.EnvMapper;
import com.lds.config.center.server.business.item.mapper.ItemMapper;
import com.lds.config.center.server.business.namespace.entity.Namespace;
import com.lds.config.center.server.business.namespace.mapper.NamespaceMapper;
import com.lds.config.center.server.common.enums.ServerErrorEnum;
import com.lds.management.center.common.model.dto.ResultDTO;
import com.lds.management.center.common.util.AssertUtil;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ClientServiceImpl implements ClientService {
    private final EnvMapper envMapper;
    private final ClusterMapper clusterMapper;
    private final AppMapper appMapper;
    private final NamespaceMapper namespaceMapper;
    private final ItemMapper itemMapper;

    /**
     * key=envName_clusterName_appName_namespaceName
     * value=客户端集合
     */
    private final Multimap<String, DeferredResult<ResultDTO<NotifyUpdateItemResponse>>> deferredResults = Multimaps.newSetMultimap(new ConcurrentHashMap<>(), ConcurrentHashMap::newKeySet);


    @Override
    public ResultDTO<GetAllItemResponse> getAllItem(GetAllItemRequest request) {
        if (Objects.nonNull(request.getEnvId()) &&
             Objects.nonNull(request.getClusterId()) &&
             Objects.nonNull(request.getAppId()) &&
             MapUtils.isNotEmpty(request.getNamespaceNameMap())) {
            return getAllItemByNamespaceId(request);
        }

        Long envId = getEnvId(request.getEnvId(), request.getEnvName());
        Long clusterId = getClusterId(request.getClusterId(), request.getClusterName(), envId);
        Long appId = getAppId(request.getAppId(), request.getAppName(), clusterId);
        updateNamespaceNameMap(request.getNamespaceNameMap(), appId);

        request.setEnvId(envId);
        request.setClusterId(clusterId);
        request.setAppId(appId);
        return getAllItemByNamespaceId(request);
    }

    private Long getEnvId(Long envId, String envName) {
        if (Objects.nonNull(envId)) {
            AssertUtil.assertTrue(envMapper.exists(new QueryWrapper<>(Env.builder().id(envId).build())), ServerErrorEnum.ENV_NOT_EXIST);
            return envId;
        }

        envId = envMapper.getEnvIdByName(envName);
        AssertUtil.assertTrue(Objects.nonNull(envId), ServerErrorEnum.ENV_NOT_EXIST);
        return envId;
    }

    private Long getClusterId(Long clusterId, String clusterName, Long envId) {
        if (Objects.nonNull(clusterId)) {
            AssertUtil.assertTrue(clusterMapper.exists(new QueryWrapper<>(Cluster.builder().id(clusterId).build())), ServerErrorEnum.CLUSTER_NOT_EXIST);
            return clusterId;
        }

        clusterId = clusterMapper.getClusterIdByEnvIdAndName(envId, clusterName);
        AssertUtil.assertTrue(Objects.nonNull(clusterId), ServerErrorEnum.CLUSTER_NOT_EXIST);
        return clusterId;
    }

    private Long getAppId(Long appId, String appName, Long clusterId) {
        if (Objects.nonNull(appId)) {
            AssertUtil.assertTrue(appMapper.exists(new QueryWrapper<>(App.builder().id(appId).build())), ServerErrorEnum.APP_NOT_EXIST);
            return appId;
        }

        appId = appMapper.getAppIdByClusterIdAndName(clusterId, appName);
        AssertUtil.assertTrue(Objects.nonNull(appId), ServerErrorEnum.APP_NOT_EXIST);
        return appId;
    }

    private void updateNamespaceNameMap(Map<String, Long> namespaceNameMap, Long appId) {
        namespaceNameMap.forEach((namespaceName, namespaceId) -> namespaceNameMap.put(namespaceName, getNamespaceId(namespaceId, namespaceName, appId)));
    }

    private Long getNamespaceId(Long namespaceId, String namespaceName, Long appId) {
        if (Objects.nonNull(namespaceId)) {
            AssertUtil.assertTrue(namespaceMapper.exists(new QueryWrapper<>(Namespace.builder().id(namespaceId).build())), ServerErrorEnum.NAMESPACE_NOT_EXIST);
            return namespaceId;
        }

        namespaceId = namespaceMapper.getNamespaceIdByAppIdAndName(appId, namespaceName);
        AssertUtil.assertTrue(Objects.nonNull(namespaceId), ServerErrorEnum.NAMESPACE_NOT_EXIST);
        return namespaceId;
    }

    private ResultDTO<GetAllItemResponse> getAllItemByNamespaceId(GetAllItemRequest request) {
        return ResultDTO.success(GetAllItemResponse.builder()
                .envId(request.getEnvId())
                .envName(request.getEnvName())
                .clusterId(request.getClusterId())
                .clusterName(request.getClusterName())
                .appId(request.getAppId())
                .appName(request.getAppName())
                .namespaceMap(namespaceMap(request.getNamespaceNameMap()))
                .build());
    }

    private Map<String, GetAllItemResponse.Namespace> namespaceMap(Map<String, Long> namespaceNameMap) {
        Map<String, GetAllItemResponse.Namespace> namespaceMap = new HashMap<>();
        namespaceNameMap.forEach((namespaceName, namespaceId) ->
                namespaceMap.put(namespaceName, getNamespace(namespaceId, namespaceName)));
        return namespaceMap;
    }

    private GetAllItemResponse.Namespace getNamespace(Long namespaceId, String namespaceName) {
        return GetAllItemResponse.Namespace.builder()
                .namespaceId(namespaceId)
                .namespaceName(namespaceName)
                .items(getAllItems(namespaceId))
                .build();
    }

    private List<GetAllItemResponse.Item> getAllItems(Long namespaceId) {
        List<Map<String, String>> items = itemMapper.getAllItemByNamespaceId(namespaceId);
        return items.stream()
                .map(this::buildItem)
                .collect(Collectors.toList());
    }

    private GetAllItemResponse.Item buildItem(Map<String, String> item) {
        return new GetAllItemResponse.Item(item.get("name"), item.get("value"));
    }

    /**
     * 通知更新
     */
    @Override
    public DeferredResult<ResultDTO<NotifyUpdateItemResponse>> notifyUpdateItem(NotifyUpdateItemRequest request) {
        DeferredResult<ResultDTO<NotifyUpdateItemResponse>> deferredResult = new DeferredResult<>(60000L, "配置文件配置项未更新");
        Set<String> watchKeys = watchKeys(request);

        deferredResult.onTimeout(() -> clearDeferredResult(watchKeys, deferredResult));
        deferredResult.onCompletion(() -> clearDeferredResult(watchKeys, deferredResult));

        addDeferredResult(watchKeys, deferredResult);

        return deferredResult;
    }

    private Set<String> watchKeys(NotifyUpdateItemRequest request) {
        Set<String> watchKeys = new HashSet<>();
        for (String namespaceName : request.getNamespaceNames().split(",")) {
            watchKeys.add(WatchKeyUtil.buildWatchKey(request.getEnvName(), request.getClusterName(), request.getAppName(), namespaceName));
        }
        return watchKeys;
    }

    /**
     * 清理deferredResult
     */
    private void clearDeferredResult(Set<String> watchKeys, DeferredResult<ResultDTO<NotifyUpdateItemResponse>> deferredResult) {
        for (String watchKey : watchKeys) {
            deferredResults.remove(watchKey, deferredResult);
        }
    }

    /**
     * 添加deferredResult
     */
    private void addDeferredResult(Set<String> watchKeys, DeferredResult<ResultDTO<NotifyUpdateItemResponse>> deferredResult) {
        for (String watchKey : watchKeys) {
            deferredResults.put(watchKey, deferredResult);
        }
    }

    /**
     * 自动通知
     */
    @Override
    public void autoNotify(NotifyUpdateItemResponse response) {
        String watchKey = WatchKeyUtil.buildWatchKey(response.getEnvName(), response.getClusterName(), response.getAppName(), response.getNamespaceName());
        Collection<DeferredResult<ResultDTO<NotifyUpdateItemResponse>>> deferredResultList = deferredResults.get(watchKey);
        if (CollectionUtils.isEmpty(deferredResultList)) return;

        deferredResultList.forEach(deferredResult -> {
            deferredResult.setResult(ResultDTO.success(response));
        });
    }

}
